package project3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import project2.ProxyServer;

public class ProxyServerWrapper {
	public static final int ARRAY_LENTH = 512;
	public static final int TIME_OUT = 50;
	public static final int TIME_OUT_INTERVAL = 500;
	private static int port; // The port which the ProxyServer binds to.
	private static Map<Integer, byte[]> bufferPool;
	private static List<Integer> activeStreamId;
	private static Map<Integer, String> ipport;
	
	// This soc is created when sending message to the proxy server.
	private Socket soc;
	private DataInputStream input;
	private DataOutputStream output;
	
	/**
	 * Throws Exception if error happens when connecting to the ProxyServer. 
	 * 
	 * @param port
	 * @throws Exception
	 */
	public ProxyServerWrapper(final int port) throws Exception {
		if (ProxyServerWrapper.bufferPool == null) {
			ProxyServerWrapper.port = port;
			ProxyServerWrapper.bufferPool = new ConcurrentHashMap<Integer, byte[]>();
			ProxyServerWrapper.ipport = new ConcurrentHashMap<Integer, String>();
			ProxyServerWrapper.activeStreamId = new ArrayList<Integer>();
			
			// Intialize a ProxyServer.
			final String[] args = new String[2];
			args[0] = port + "";
			args[1] = "-n";
			new Thread() {
				
				@Override
				public void run() {
					int newPort = port;
					for (int i = 0; i < 10; i++) {
						ProxyServer.main(args);
						if (ProxyServer.getReaddy())
							break;
						else  {
							System.out.println("[PSWrapper] Port " + newPort + " has been used. ");
							newPort ++;
							args[0] = newPort + "";
						}
					}
				}
			}.start();

			while (!ProxyServer.getReaddy()) {
				Thread.sleep(50);
			}
		}
	}
	
	public void handleRequest(final int streamid, final byte[] httpRequest) {
		try {
			soc = new Socket("127.0.0.1", port);
			input = new DataInputStream(soc.getInputStream());
			output = new DataOutputStream(soc.getOutputStream());
		} catch (Exception e) {
			System.out.println("[PServer] Error when binding the port. ");
			e.printStackTrace();
		}
		activeStreamId.add(streamid);
		new Thread() {
			
			@Override
			public void run() {
				synchronized (activeStreamId) {
					try {
						output.write(httpRequest);				
					} catch (Exception e) {
						System.out.println("[PSWrapper] Fail to send the request.");
					}
				}
			}
		}.run();
	}
	
	public byte[] response(Integer streamid) {
		try {
			// Read the input data from the ProxyServer
			List<Byte> responses = new LinkedList<Byte>();
			byte b;
			try {
				System.out.println("[PServer] Reading new response. ");
				while ((b = input.readByte()) != -1) {
					responses.add(b);
				}
			} catch (EOFException e) {
				// Finish Reading
			}
			
			if (responses.size() == 0) {
				// Response size is zero.
			}
			// Put this into the bufferPool
			byte[] ret = new byte[responses.size()];
			for (int i = 0; i < responses.size(); i++) {
				ret[i] = responses.get(i);
			}
			System.out.println("[PServer] New Response: ");
			soc.close();
			input.close();
			output.close();
			return ret;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	// Register the map from the stream id to the hostname:port if we can connect to the hostname:port
	public boolean registerStreamId(RelayCell cell) {
		// TODO: Make sure that the new String(cell.getBody()) will get the hosename:port 
		if (ipport.containsKey(cell.getStreamId())) {
			return false;
		}
		ipport.put(cell.getStreamId(), new String(cell.getBody()));
		return true;
	}

	public static void endStreamWithWebserver(RelayCell cell) {
		if (ipport != null && ipport.containsKey(cell.getStreamId()))
			ipport.remove(cell.getStreamId());
	}
}
